// gcc -g -o cf cf.c && ./cf t1.w

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

struct entry {
	char *name;
	void *code;
	void (*interpreter)(void *);
	struct entry *next;
};

struct entry *dictionary = NULL;

void init_vm(void);
struct entry *add_word(char *name, void *, void (*interpreter)(void *));
struct entry *find_word(char *name);
char *read_word(FILE *fptr);
struct entry **load_code(FILE *fptr);
void interpret(void *);

#define STACK_SIZE 1000
uint64_t stack[STACK_SIZE];
uint64_t *stack_ptr;

int main(int argc, char **argv) {
	FILE *fptr;
	
	if(argc != 2) {
		puts("./cf <filename>");
		return 1;
	}
	
	fptr = fopen(argv[1], "r");
	if(!fptr) {
		puts("open");
		return 2;
	}

	init_vm();
	interpret(load_code(fptr));
	
	return 0;
}

void bltn_one(void *code) {
	stack_ptr++;
	*stack_ptr = 1;
}

void bltn_dot(void *code) {
	printf("%lld\n", *stack_ptr);
	stack_ptr--;
}

void bltn_add(void *code) {
	uint64_t tmp;

	tmp = *stack_ptr;
	stack_ptr--;
	stack_ptr[0] += tmp;
}

void bltn_dup(void *code) {
	uint64_t tmp;

	tmp = *stack_ptr;
	stack_ptr++;
	*stack_ptr = tmp;
}

void init_vm(void) {
	stack_ptr = stack;
	
	add_word("one", NULL, bltn_one);
	add_word("dot", NULL, bltn_dot);
	add_word("add", NULL, bltn_add);
	add_word("dup", NULL, bltn_dup);
}

struct entry *add_word(char *name, void *code, void (*interpreter)(void *)) {
	struct entry *ent;

	ent = malloc(sizeof(struct entry));
	ent->name = name;
	ent->code = code;
	ent->interpreter = interpreter;
	ent->next = dictionary;
	
	dictionary = ent;

	return ent;
}

void bltn_lit(void *code) {
	uint64_t *n;

	n = code;
	
	stack_ptr++;
	*stack_ptr = n[0];
}

struct entry *find_word(char *name) {
	struct entry *ent;

	ent = dictionary;

	while(ent) {
		if(ent->name && !strcmp(ent->name, name)) {
			return ent;
		}
		
		ent = ent->next;
	}
}

char *read_word(FILE *fptr) {
	int c, i;

	static char *wbuf;
	static int wlen;
	
	if(feof(fptr)) {
		return NULL;
	}

	c = fgetc(fptr);
	if(c == EOF) {
		return NULL;
	}

	if(!wbuf) {
		wlen = 64;
		wbuf = malloc(wlen);
	}
	
	i = 0;
	do {
		wbuf[i++] = c;
		
		if(!c) break;
		
		if(i > wlen) {
			wlen *= 2;
			wbuf = realloc(wbuf, wlen);
		}
	} while((c = fgetc(fptr)) != EOF);
	
	return wbuf;
}

struct entry ** load_code(FILE *fptr) {
	char *word;
	struct entry *ent;
	
	struct entry **code;
	int code_len;
	
        char *sub_name;
	struct entry **sub_code;
	
	int i;

	uint64_t tmp, *tmp2;

	code_len = 1;
	code = malloc(code_len * sizeof(struct entry *));
	i = 0;
	
	while(word = read_word(fptr)) {
		//printf("read word <%s>\n", word);

		// IMMEDIATES are hard coded
		// in a proper forth you can define your own immediates
		
		if(!strcmp(";", word)) {
			code[i] = NULL;
			return code;
		}
		else if(!strcmp(":", word)) {
			sub_name = strdup(read_word(fptr));
			sub_code = load_code(fptr);
			add_word(sub_name, sub_code, interpret);
			
			continue;
		}
		else if(!strcmp("lit", word)) {
			word = read_word(fptr);
			if(1 != sscanf(word, "%lld", &tmp)) {
			        printf("couldn't read a number from <%s>\n", word);
				exit(-1);
			}
 
			tmp2 = malloc(sizeof(uint64_t));
			tmp2[0] = tmp;
			
			ent = add_word(NULL, tmp2, bltn_lit);
		}
		else {
			ent = find_word(word);
			
			if(!ent) {
				printf("Could not find word <%s>\n", word);
				exit(-1);
			}
		}
		
		code[i++] = ent;
		if(i > code_len) {
			code_len *= 2;
			code = realloc(code, code_len * sizeof(struct entry*));
		}
	}
	
	code[i] = NULL;

	return code;
}

void interpret(void *code_v) {
	struct entry **code = code_v;
	struct entry *ent;

	while(ent = *code++) { ent->interpreter(ent->code); }
}
